Terraformで構築したECSをCodePipeline等でローリング更新するとタスク定義のリビジョンがずれる問題をdataを使って回避する
「CodePipelineでECSをローリング更新した時に、Terraformと実際のリビジョンがずれてplanやapply時に差異が出る」
TerraformでECSを管理して、デプロイは他のツールを使っているパターンがあると思います。(例: CodePipeline,GithubActions,CircleCI等)
TerraformでECSを作成して、コンテナのデプロイにはCodePipeline(ローリング更新)を使っていました。
CodePipelineとTerraformタスク定義のリビジョンがズレてしまって、Terrformを変更していないのに terraform plan の際に差分が出てしまうことがありました。
結論: dataを使ってECSタスク定義の最新のarnを取得しましょう。
resource "aws_ecs_service" "mongo" { name = "mongo" cluster = aws_ecs_cluster.foo.id desired_count = 2 # Track the latest ACTIVE revision task_definition = data.aws_ecs_task_definition.mongo.arn }
aws_ecs_task_definition | Data Sources | hashicorp/aws | Terraform Registry
事象: タスク定義のリビジョンがずれる
タスク定義のリビジョンがずれるパターンのECSのtfファイルです。
resource "aws_ecs_cluster" "this" { name = "${local.name_prefix}-cluster" setting { name = "containerInsights" value = "enabled" } } resource "aws_ecs_service" "sample_app" { name = local.name_prefix cluster = aws_ecs_cluster.this.arn launch_type = "FARGATE" # terraformで作成したタスク定義のARNを直接指定 task_definition = aws_ecs_task_definition.sample_app.arn desired_count = 1 platform_version = "1.4.0" network_configuration { assign_public_ip = false security_groups = [module.ecs_sg.security_group_id] subnets = module.vpc.private_subnets } load_balancer { target_group_arn = aws_lb_target_group.sample_app.arn container_name = "httpd" container_port = 80 } deployment_circuit_breaker { enable = true rollback = true } lifecycle { ignore_changes = [ desired_count ] } } resource "aws_ecs_task_definition" "sample_app" { family = local.name_prefix cpu = 256 memory = 512 network_mode = "awsvpc" requires_compatibilities = ["FARGATE"] container_definitions = templatefile("./file/container_definitions.json", { ecr_repository_url : aws_ecr_repository.httpd.repository_url, }) execution_role_arn = aws_iam_role.ecs_tasks.arn }
このファイルを使って、リソースを作成してCodePipelineでローリング更新を行います。
その後、terraform側のコードは変更せず terraform planで差分を確認します。
$ terraform plan # 出力結果から抜粋 # aws_ecs_service.sample_app will be updated in-place ~ resource "aws_ecs_service" "sample_app" { id = "arn:aws:ecs:ap-northeast-1:00000000000:service/ecs-rolling-update-cluster/ecs-rolling-update" name = "ecs-rolling-update" tags = {} ~ task_definition = "arn:aws:ecs:ap-northeast-1:00000000000:task-definition/ecs-rolling-update:12" -> "arn:aws:ecs:ap-northeast-1:00000000000:task-definition/ecs-rolling-update:11" # (14 unchanged attributes hidden) # (4 unchanged blocks hidden) }
terraform側でデプロイしたタスク定義のリビジョンが11で、CodePipelineでローリング更新した時に12になったため差分が発生しています。
解決策: dataを使って最新のタスク定義を取得するように取得するように修正
サービスにタスク定義を渡す部分を変更して、dataから最新のタスク定義を取得するようにします。
resource "aws_ecs_cluster" "this" { name = "${local.name_prefix}-cluster" setting { name = "containerInsights" value = "enabled" } } resource "aws_ecs_service" "sample_app" { name = local.name_prefix cluster = aws_ecs_cluster.this.arn launch_type = "FARGATE" # CodePipelineでデプロイ時にリビジョンが更新されるため、最新のrevisionをdataで取得 task_definition = data.aws_ecs_task_definition.sample_app.arn desired_count = 1 platform_version = "1.4.0" network_configuration { assign_public_ip = false security_groups = [module.ecs_sg.security_group_id] subnets = module.vpc.private_subnets } load_balancer { target_group_arn = aws_lb_target_group.sample_app.arn container_name = "httpd" container_port = 80 } deployment_circuit_breaker { enable = true rollback = true } lifecycle { ignore_changes = [desired_count] } } resource "aws_ecs_task_definition" "sample_app" { family = local.name_prefix cpu = 256 memory = 512 network_mode = "awsvpc" requires_compatibilities = ["FARGATE"] container_definitions = templatefile("./file/container_definitions.json", { ecr_repository_url : aws_ecr_repository.httpd.repository_url, }) execution_role_arn = aws_iam_role.ecs_tasks.arn } data "aws_ecs_task_definition" "sample_app" { task_definition = aws_ecs_task_definition.sample_app.family }
差分が出るパターンと同様の環境で、planを実行して差分が出ないことを確認できました。
$ terraform plan
補足: aws_ecs_serviceのlifecycleのignore_changesにタスク定義を含めるパターン
ECS Service側でタスク定義をlifecycleでignore_changesにすることで、差分が出なくはなります。
今回はタスク定義をTerraformで管理しているためTerraform側で変更した時にはECSのサービスも更新したかったため、dataを使いました。
ローリング更新の場合、CodeBuild上でコンテナをビルドしてファイル(imagedefinitions.json)にイメージのURIを書き込みます。 そのファイルを元に、タスク定義のイメージの部分を更新します。
この際に更新対象となるタスク定義は、サービスで稼働しているタスク定義になります。
lifecycleの方法では、タスク定義のリビジョンは増えますが稼働しているサービスのタスク定義は変わりません。 (更新したタスク定義を使って、CodePipelineでデプロイできない)
そのため、タスク定義を更新した場合はサービスのタスク定義を手動で更新する必要があります。
おわりに
Terraform管理のタスク定義をTerraform以外でECSデプロイするとリビジョンがずれる問題でした。
AWS Providerバージョン 3.70 まではdataのAttributesにarnが無かったため、以下の書き方をする必要がありました。
現在は、arnも追加されてスッキリ書けるようになっています。
resource "aws_ecs_service" "mongo" { name = "mongo" cluster = aws_ecs_cluster.foo.id desired_count = 2 # Track the latest ACTIVE revision task_definition = "${aws_ecs_task_definition.mongo.family}:${max(aws_ecs_task_definition.mongo.revision, data.aws_ecs_task_definition.mongo.revision)}" }
この件でサンプルコードの1行修正ですが、terraform-provider-awsにコントリビュートしました。 今後もチャンスがあれば、貢献していきたいです。
以上、AWS事業本部の佐藤(@chari7311)でした。